import sys
import os
import cv2
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QPoint, QReadWriteLock
from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QColor
from PyQt5.QtWidgets import (QMainWindow, QApplication, QFileDialog, QLabel, QSlider,
                             QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QTabWidget,
                             QComboBox, QGroupBox, QFormLayout, QSpinBox, QDoubleSpinBox,
                             QMessageBox, QProgressBar, QDialog, QGridLayout, QCheckBox)

# 确保中文显示正常
import matplotlib

matplotlib.use('Agg')
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]


# 视频播放线程类 - 优化线程安全和资源管理
class VideoPlayerThread(QThread):
    update_frame_signal = pyqtSignal(np.ndarray)  # 发送视频帧信号
    error_signal = pyqtSignal(str)  # 错误信号
    finished_signal = pyqtSignal()  # 线程结束信号

    def __init__(self, video_path):
        super().__init__()
        self.video_path = video_path
        self.is_running = False
        self.frame_processing_func = None  # 可选的帧处理函数
        self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        self.max_frame_size = 1024  # 最大帧尺寸限制

    def set_processing_function(self, func):
        """设置帧处理函数"""
        self.frame_processing_func = func

    def run(self):
        self.is_running = True
        cap = cv2.VideoCapture(self.video_path)

        if not cap.isOpened():
            self.error_signal.emit(f"无法打开视频文件: {self.video_path}")
            self.finished_signal.emit()
            return

        fps = cap.get(cv2.CAP_PROP_FPS)
        self.ms_per_frame = int(1000 / fps) if fps > 0 else 40  # 默认25fps

        while self.is_running:
            ret, frame = cap.read()
            if not ret:
                self.error_signal.emit("视频读取完毕或发生错误")
                break

            # 限制帧尺寸，防止内存溢出
            frame = self._limit_frame_size(frame)

            # 应用帧处理函数（如有）
            if self.frame_processing_func:
                try:
                    frame = self.frame_processing_func(frame)
                except Exception as e:
                    self.error_signal.emit(f"帧处理错误: {str(e)}")
                    break

            self.update_frame_signal.emit(frame)  # 发送处理后的帧
            self.msleep(self.ms_per_frame)  # 控制播放速度

        cap.release()
        self.is_running = False
        self.finished_signal.emit()

    def _limit_frame_size(self, frame):
        """限制帧尺寸以避免内存问题"""
        height, width = frame.shape[:2]
        if max(height, width) > self.max_frame_size:
            scale = self.max_frame_size / max(height, width)
            frame = cv2.resize(frame, (int(width * scale), int(height * scale)))
        return frame

    def stop(self):
        """安全停止线程"""
        self.is_running = False
        self.wait()  # 等待线程完全停止

    def detect_faces_in_frame(self, frame):
        """在视频帧中检测人脸"""
        if frame is None or frame.size == 0:
            return frame

        try:
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            faces = self.face_cascade.detectMultiScale(
                gray,
                scaleFactor=1.1,
                minNeighbors=5,
                minSize=(30, 30),
                flags=cv2.CASCADE_SCALE_IMAGE
            )

            for (x, y, w, h) in faces:
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
                cv2.putText(frame, "人脸", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

            return frame
        except Exception as e:
            self.error_signal.emit(f"人脸检测错误: {str(e)}")
            return frame


# 摄像头处理线程 - 优化资源管理和错误处理
class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(np.ndarray)
    error_signal = pyqtSignal(str)
    finished_signal = pyqtSignal()

    def __init__(self, processor, algorithm="None"):
        super().__init__()
        self.processor = processor
        self.algorithm = algorithm
        self._run_flag = True
        self.max_frame_size = 1024  # 最大帧尺寸限制

    def run(self):
        # 打开摄像头
        cap = cv2.VideoCapture(0)

        if not cap.isOpened():
            self.error_signal.emit("无法打开摄像头")
            self.finished_signal.emit()
            return

        fps = cap.get(cv2.CAP_PROP_FPS)
        self.ms_per_frame = int(1000 / fps) if fps > 0 else 40  # 默认25fps

        while self._run_flag:
            ret, frame = cap.read()
            if not ret:
                self.error_signal.emit("摄像头读取失败")
                break

            # 限制帧尺寸，防止内存溢出
            frame = self._limit_frame_size(frame)

            # 应用选定的算法
            if self.algorithm == "FaceDetection":
                try:
                    processed_frame, _, _ = self.processor.detect_faces()
                    frame = processed_frame if processed_frame is not None else frame
                except Exception as e:
                    self.error_signal.emit(f"人脸检测错误: {str(e)}")
            elif self.algorithm == "ShapeDetection":
                try:
                    processed_frame, _, _ = self.processor.detect_shapes()
                    frame = processed_frame if processed_frame is not None else frame
                except Exception as e:
                    self.error_signal.emit(f"形状检测错误: {str(e)}")

            self.change_pixmap_signal.emit(frame)
            self.msleep(self.ms_per_frame)  # 控制帧率

        # 释放摄像头
        cap.release()
        self._run_flag = False
        self.finished_signal.emit()

    def _limit_frame_size(self, frame):
        """限制帧尺寸以避免内存问题"""
        height, width = frame.shape[:2]
        if max(height, width) > self.max_frame_size:
            scale = self.max_frame_size / max(height, width)
            frame = cv2.resize(frame, (int(width * scale), int(height * scale)))
        return frame

    def stop(self):
        """安全停止线程"""
        self._run_flag = False
        self.wait()  # 等待线程完全停止


# 基础图像处理功能类 - 优化内存管理和线程安全
class ImageProcessor:
    def __init__(self):
        self.original_image = None
        self.current_image = None
        self.display_image = None
        self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        self.crop_start = None
        self.crop_end = None

        # 线程锁
        self.image_lock = QReadWriteLock()  # 图像数据锁
        self.crop_lock = QReadWriteLock()  # 裁剪状态锁

        # 最大图像尺寸限制（防止大图像导致内存溢出）
        self.max_image_size = 2048

    def load_image(self, file_path):
        """安全加载图像并限制尺寸"""
        if not os.path.exists(file_path):
            return None, None

        self.image_lock.lockForWrite()
        try:
            img = cv2.imread(file_path)
            if img is None:
                return None, None

            # 限制图像尺寸
            img = self._limit_image_size(img)

            self.original_image = img
            self.current_image = img.copy()
            self.display_image = img.copy()
            self.crop_start = None
            self.crop_end = None

            return self.current_image, self.original_image
        finally:
            self.image_lock.unlock()

    def _limit_image_size(self, img):
        """限制图像尺寸以避免内存问题"""
        height, width = img.shape[:2]
        if max(height, width) > self.max_image_size:
            scale = self.max_image_size / max(height, width)
            img = cv2.resize(img, (int(width * scale), int(height * scale)))
        return img

    def rotate(self, angle):
        """安全旋转图像"""
        self.image_lock.lockForWrite()
        try:
            if self.current_image is None:
                return None, None

            rows, cols = self.current_image.shape[:2]
            M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1)
            self.current_image = cv2.warpAffine(self.current_image, M, (cols, rows))
            self.display_image = self.current_image.copy()

            return self.current_image, self.original_image
        finally:
            self.image_lock.unlock()

    def resize(self, scale_percent):
        """安全调整图像大小"""
        self.image_lock.lockForWrite()
        try:
            if self.current_image is None:
                return None, None

            width = int(self.current_image.shape[1] * scale_percent / 100)
            height = int(self.current_image.shape[0] * scale_percent / 100)
            dim = (width, height)
            self.current_image = cv2.resize(self.current_image, dim, interpolation=cv2.INTER_AREA)
            self.display_image = self.current_image.copy()

            return self.current_image, self.original_image
        finally:
            self.image_lock.unlock()

    def start_crop(self, x, y):
        """开始裁剪操作"""
        self.crop_lock.lockForWrite()
        try:
            if self.current_image is not None:
                self.crop_start = (x, y)
        finally:
            self.crop_lock.unlock()

    def update_crop(self, x, y):
        """更新裁剪区域"""
        self.image_lock.lockForWrite()
        self.crop_lock.lockForWrite()
        try:
            if (self.current_image is not None and
                    self.crop_start is not None and
                    self.crop_end != (x, y)):  # 避免重复操作

                self.crop_end = (x, y)
                self.display_image = self.current_image.copy()

                # 绘制选框
                cv2.rectangle(self.display_image, self.crop_start, self.crop_end, (0, 255, 0), 2)

                return self.display_image, self.original_image
            return None, None
        finally:
            self.crop_lock.unlock()
            self.image_lock.unlock()

    def apply_crop(self):
        """应用裁剪"""
        self.image_lock.lockForWrite()
        self.crop_lock.lockForWrite()
        try:
            if (self.current_image is not None and
                    self.crop_start is not None and
                    self.crop_end is not None):

                x1, y1 = self.crop_start
                x2, y2 = self.crop_end

                # 确保坐标顺序正确
                x_min, x_max = min(x1, x2), max(x1, x2)
                y_min, y_max = min(y1, y2), max(y1, y2)

                # 确保不超出边界
                x_min = max(0, x_min)
                y_min = max(0, y_min)
                x_max = min(self.current_image.shape[1], x_max)
                y_max = min(self.current_image.shape[0], y_max)

                # 裁剪图像
                if x_max > x_min and y_max > y_min:
                    self.current_image = self.current_image[y_min:y_max, x_min:x_max]
                    self.display_image = self.current_image.copy()
                    self.crop_start = None
                    self.crop_end = None

                    return self.current_image, self.original_image
            return None, None
        finally:
            self.crop_lock.unlock()
            self.image_lock.unlock()

    def cancel_crop(self):
        """取消裁剪操作"""
        self.image_lock.lockForWrite()
        self.crop_lock.lockForWrite()
        try:
            if self.current_image is not None:
                self.display_image = self.current_image.copy()
                self.crop_start = None
                self.crop_end = None

                return self.display_image, self.original_image
            return None, None
        finally:
            self.crop_lock.unlock()
            self.image_lock.unlock()

    def adjust_brightness_contrast(self, brightness=0, contrast=0, saturation=0):
        """调整亮度、对比度和饱和度"""
        self.image_lock.lockForWrite()
        try:
            if self.current_image is None:
                return None, None

            img = self.current_image.copy()

            # 调整亮度和对比度
            if brightness != 0 or contrast != 0:
                if brightness > 0:
                    shadow = brightness
                    highlight = 255
                else:
                    shadow = 0
                    highlight = 255 + brightness

                alpha_b = (highlight - shadow) / 255
                gamma_b = shadow
                buf = cv2.addWeighted(img, alpha_b, img, 0, gamma_b)

                if contrast != 0:
                    f = 131 * (contrast + 127) / (127 * (131 - contrast))
                    alpha_c = f
                    gamma_c = 127 * (1 - f)
                    buf = cv2.addWeighted(buf, alpha_c, buf, 0, gamma_c)

                img = buf

            # 调整饱和度 (仅对彩色图像有效)
            if saturation != 0 and len(img.shape) == 3:
                hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
                h, s, v = cv2.split(hsv)

                # 增加或减少饱和度
                if saturation > 0:
                    limit = 255 - saturation
                    s[s > limit] = 255
                    s[s <= limit] += saturation
                else:
                    s[s < -saturation] = 0
                    s[s >= -saturation] -= abs(saturation)

                hsv = cv2.merge((h, s, v))
                img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

            self.current_image = img
            self.display_image = self.current_image.copy()

            return self.current_image, self.original_image
        finally:
            self.image_lock.unlock()

    def smooth(self, method="gaussian", kernel_size=3):
        """图像平滑处理"""
        self.image_lock.lockForWrite()
        try:
            if self.current_image is None:
                return None, None

            if method == "gaussian":
                self.current_image = cv2.GaussianBlur(self.current_image, (kernel_size, kernel_size), 0)
            elif method == "median":
                self.current_image = cv2.medianBlur(self.current_image, kernel_size)
            elif method == "bilateral":
                # 双边滤波保留边缘的同时平滑图像
                self.current_image = cv2.bilateralFilter(self.current_image, kernel_size, 75, 75)

            self.display_image = self.current_image.copy()

            return self.current_image, self.original_image
        finally:
            self.image_lock.unlock()

    def detect_shapes(self):
        """形状检测"""
        self.image_lock.lockForWrite()
        try:
            if self.current_image is None:
                return None, None, []

            output = self.current_image.copy()
            gray = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2GRAY)

            # 使用自适应阈值提高检测效果
            thresh = cv2.adaptiveThreshold(
                gray, 255,
                cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                cv2.THRESH_BINARY_INV,
                11, 2
            )

            # 寻找轮廓
            contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

            shapes = []
            for contour in contours:
                epsilon = 0.01 * cv2.arcLength(contour, True)
                approx = cv2.approxPolyDP(contour, epsilon, True)
                x, y, w, h = cv2.boundingRect(approx)

                shape = "未识别"
                if len(approx) == 3:
                    shape = "三角形"
                elif len(approx) == 4:
                    shape = "矩形"
                elif len(approx) > 4:
                    area = cv2.contourArea(contour)
                    ((x_c, y_c), radius) = cv2.minEnclosingCircle(contour)
                    circle_area = np.pi * radius * radius
                    if abs(area - circle_area) < 0.1 * circle_area:
                        shape = "圆形"
                    else:
                        shape = "多边形"

                shapes.append(shape)
                cv2.drawContours(output, [contour], -1, (0, 255, 0), 2)
                cv2.putText(output, shape, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)

            self.current_image = output
            self.display_image = self.current_image.copy()

            return self.current_image, self.original_image, shapes
        finally:
            self.image_lock.unlock()

    def stitch_images(self, images):
        """图像拼接"""
        if len(images) < 2:
            return None, None

        # 限制每张图像的最大尺寸
        scaled_images = []
        for img in images:
            if img is not None and img.size > 0:
                scaled_images.append(self._limit_image_size(img))

        if not scaled_images:
            return None, None

        try:
            # 使用OpenCV的图像拼接器
            stitcher = cv2.Stitcher_create()
            status, result = stitcher.stitch(scaled_images)

            if status == cv2.Stitcher_OK:
                self.image_lock.lockForWrite()
                try:
                    # 最终结果也限制尺寸
                    result = self._limit_image_size(result)
                    self.current_image = result
                    self.display_image = self.current_image.copy()
                    return self.current_image, None  # 拼接后没有原始图像对比
                finally:
                    self.image_lock.unlock()
            else:
                print(f"拼接失败，状态码: {status}")
                return None, None
        except Exception as e:
            print(f"图像拼接异常: {str(e)}")
            return None, None

    # 基于OpenCV Haar级联分类器的人脸检测
    def detect_faces(self):
        """人脸检测"""
        self.image_lock.lockForWrite()
        try:
            if self.current_image is None:
                return None, None, 0

            gray = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2GRAY)
            faces = self.face_cascade.detectMultiScale(
                gray,
                scaleFactor=1.1,
                minNeighbors=5,
                minSize=(30, 30),
                flags=cv2.CASCADE_SCALE_IMAGE
            )

            output = self.current_image.copy()
            for (x, y, w, h) in faces:
                cv2.rectangle(output, (x, y), (x + w, y + h), (0, 255, 0), 2)
                cv2.putText(output, "人脸", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

            self.current_image = output
            self.display_image = self.current_image.copy()

            return self.current_image, self.original_image, len(faces)
        finally:
            self.image_lock.unlock()


# 可点击的图像显示区域，用于裁剪功能
class ClickableImageLabel(QLabel):
    mouse_pressed = pyqtSignal(int, int)
    mouse_moved = pyqtSignal(int, int)
    mouse_released = pyqtSignal(int, int)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMouseTracking(True)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            x = event.pos().x()
            y = event.pos().y()
            self.mouse_pressed.emit(x, y)

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            x = event.pos().x()
            y = event.pos().y()
            self.mouse_moved.emit(x, y)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            x = event.pos().x()
            y = event.pos().y()
            self.mouse_released.emit(x, y)


# 主窗口类 - 优化UI响应和资源管理
class CVPlatform(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("计算机视觉开发平台")
        self.resize(1200, 800)

        self.processor = ImageProcessor()
        self.video_thread = None  # 摄像头线程
        self.video_player = None  # 视频播放线程
        self.current_video_path = ""  # 当前播放的视频路径
        self.is_cropping = False
        self.show_original = True  # 控制是否显示原图

        # 创建主布局
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.main_layout = QHBoxLayout(self.central_widget)

        # 创建左侧面板（工具栏和参数设置）
        self.left_panel = QVBoxLayout()

        # 文件操作区
        self.file_group = QGroupBox("文件操作")
        self.file_layout = QVBoxLayout()

        self.load_image_btn = QPushButton("加载图像")
        self.load_image_btn.clicked.connect(self.load_image)

        self.load_video_btn = QPushButton("加载视频")
        self.load_video_btn.clicked.connect(self.load_video)

        self.start_camera_btn = QPushButton("打开摄像头")
        self.start_camera_btn.clicked.connect(self.start_camera)

        # 新增播放控制按钮
        self.play_pause_btn = QPushButton("播放")
        self.play_pause_btn.clicked.connect(self.toggle_play_pause)
        self.play_pause_btn.setEnabled(False)  # 初始禁用，直到加载视频

        self.stop_btn = QPushButton("停止")
        self.stop_btn.clicked.connect(self.stop_video)
        self.stop_btn.setEnabled(False)  # 初始禁用

        self.save_image_btn = QPushButton("保存图像")
        self.save_image_btn.clicked.connect(self.save_image)

        self.file_layout.addWidget(self.load_image_btn)
        self.file_layout.addWidget(self.load_video_btn)
        self.file_layout.addWidget(self.start_camera_btn)
        self.file_layout.addWidget(self.play_pause_btn)
        self.file_layout.addWidget(self.stop_btn)
        self.file_layout.addWidget(self.save_image_btn)
        self.file_group.setLayout(self.file_layout)

        # 图像处理区
        self.processing_group = QGroupBox("图像处理")
        self.processing_layout = QVBoxLayout()

        # 旋转控制
        self.rotate_layout = QHBoxLayout()
        self.rotate_label = QLabel("旋转角度:")
        self.rotate_spinbox = QSpinBox()
        self.rotate_spinbox.setRange(-180, 180)
        self.rotate_spinbox.setValue(0)
        self.rotate_btn = QPushButton("旋转")
        self.rotate_btn.clicked.connect(self.rotate_image)

        self.rotate_layout.addWidget(self.rotate_label)
        self.rotate_layout.addWidget(self.rotate_spinbox)
        self.rotate_layout.addWidget(self.rotate_btn)

        # 缩放控制
        self.resize_layout = QHBoxLayout()
        self.resize_label = QLabel("缩放比例(%):")
        self.resize_spinbox = QSpinBox()
        self.resize_spinbox.setRange(10, 500)
        self.resize_spinbox.setValue(100)
        self.resize_btn = QPushButton("缩放")
        self.resize_btn.clicked.connect(self.resize_image)

        self.resize_layout.addWidget(self.resize_label)
        self.resize_layout.addWidget(self.resize_spinbox)
        self.resize_layout.addWidget(self.resize_btn)

        # 裁剪控制
        self.crop_btn = QPushButton("开始裁剪")
        self.crop_btn.clicked.connect(self.toggle_crop_mode)

        self.apply_crop_btn = QPushButton("应用裁剪")
        self.apply_crop_btn.setEnabled(False)
        self.apply_crop_btn.clicked.connect(self.apply_crop)

        self.cancel_crop_btn = QPushButton("取消裁剪")
        self.cancel_crop_btn.setEnabled(False)
        self.cancel_crop_btn.clicked.connect(self.cancel_crop)

        self.crop_layout = QHBoxLayout()
        self.crop_layout.addWidget(self.crop_btn)
        self.crop_layout.addWidget(self.apply_crop_btn)
        self.crop_layout.addWidget(self.cancel_crop_btn)

        # 色彩调整控制
        self.brightness_layout = QHBoxLayout()
        self.brightness_label = QLabel("亮度:")
        self.brightness_slider = QSlider(Qt.Horizontal)
        self.brightness_slider.setRange(-100, 100)
        self.brightness_slider.setValue(0)
        self.brightness_value_label = QLabel("0")
        self.brightness_btn = QPushButton("应用")
        self.brightness_btn.clicked.connect(self.adjust_color)

        self.brightness_layout.addWidget(self.brightness_label)
        self.brightness_layout.addWidget(self.brightness_slider)
        self.brightness_layout.addWidget(self.brightness_value_label)
        self.brightness_layout.addWidget(self.brightness_btn)

        self.contrast_layout = QHBoxLayout()
        self.contrast_label = QLabel("对比度:")
        self.contrast_slider = QSlider(Qt.Horizontal)
        self.contrast_slider.setRange(-100, 100)
        self.contrast_slider.setValue(0)
        self.contrast_value_label = QLabel("0")

        self.contrast_layout.addWidget(self.contrast_label)
        self.contrast_layout.addWidget(self.contrast_slider)
        self.contrast_layout.addWidget(self.contrast_value_label)

        self.saturation_layout = QHBoxLayout()
        self.saturation_label = QLabel("饱和度:")
        self.saturation_slider = QSlider(Qt.Horizontal)
        self.saturation_slider.setRange(-100, 100)
        self.saturation_slider.setValue(0)
        self.saturation_value_label = QLabel("0")

        self.saturation_layout.addWidget(self.saturation_label)
        self.saturation_layout.addWidget(self.saturation_slider)
        self.saturation_layout.addWidget(self.saturation_value_label)

        # 平滑处理控制
        self.smooth_layout = QHBoxLayout()
        self.smooth_label = QLabel("平滑方法:")
        self.smooth_combo = QComboBox()
        self.smooth_combo.addItems(["高斯模糊", "中值滤波", "双边滤波"])
        self.smooth_spinbox = QSpinBox()
        self.smooth_spinbox.setRange(1, 15)
        self.smooth_spinbox.setValue(3)
        self.smooth_spinbox.setSingleStep(2)
        self.smooth_btn = QPushButton("应用")
        self.smooth_btn.clicked.connect(self.smooth_image)

        # 原图对比控制
        self.compare_layout = QHBoxLayout()
        self.compare_checkbox = QCheckBox("显示原图")
        self.compare_checkbox.setChecked(self.show_original)
        self.compare_checkbox.stateChanged.connect(self.toggle_original_view)

        self.compare_layout.addWidget(self.compare_checkbox)

        self.processing_layout.addLayout(self.rotate_layout)
        self.processing_layout.addLayout(self.resize_layout)
        self.processing_layout.addLayout(self.crop_layout)
        self.processing_layout.addLayout(self.brightness_layout)
        self.processing_layout.addLayout(self.contrast_layout)
        self.processing_layout.addLayout(self.saturation_layout)
        self.processing_layout.addLayout(self.smooth_layout)
        self.processing_layout.addLayout(self.compare_layout)
        self.processing_group.setLayout(self.processing_layout)

        # 算法应用区
        self.algorithm_group = QGroupBox("算法应用")
        self.algorithm_layout = QVBoxLayout()

        self.shape_detection_btn = QPushButton("形状识别")
        self.shape_detection_btn.clicked.connect(self.detect_shapes)

        self.face_detection_btn = QPushButton("人脸检测")
        self.face_detection_btn.clicked.connect(self.detect_faces)

        self.stitch_images_btn = QPushButton("图像拼接")
        self.stitch_images_btn.clicked.connect(self.stitch_images)

        self.algorithm_layout.addWidget(self.shape_detection_btn)
        self.algorithm_layout.addWidget(self.face_detection_btn)
        self.algorithm_layout.addWidget(self.stitch_images_btn)
        self.algorithm_group.setLayout(self.algorithm_layout)

        # 视频处理区
        self.video_group = QGroupBox("视频处理")
        self.video_layout = QVBoxLayout()

        self.video_algorithm_combo = QComboBox()
        self.video_algorithm_combo.addItems(["None", "FaceDetection", "ShapeDetection"])

        self.apply_video_algorithm_btn = QPushButton("应用算法到视频")
        self.apply_video_algorithm_btn.clicked.connect(self.apply_video_algorithm)

        self.video_layout.addWidget(QLabel("选择视频算法:"))
        self.video_layout.addWidget(self.video_algorithm_combo)
        self.video_layout.addWidget(self.apply_video_algorithm_btn)
        self.video_group.setLayout(self.video_layout)

        self.left_panel.addWidget(self.file_group)
        self.left_panel.addWidget(self.processing_group)
        self.left_panel.addWidget(self.algorithm_group)
        self.left_panel.addWidget(self.video_group)
        self.left_panel.addStretch()

        # 创建中央显示区域
        self.display_group = QGroupBox("显示区域")
        self.display_layout = QHBoxLayout()

        # 原图显示区域
        self.original_display = QLabel()
        self.original_display.setAlignment(Qt.AlignCenter)
        self.original_display.setMinimumSize(320, 240)
        self.original_display.setText("原图")

        # 处理后图像显示区域
        self.image_display = ClickableImageLabel()
        self.image_display.setAlignment(Qt.AlignCenter)
        self.image_display.setMinimumSize(320, 240)
        self.image_display.setText("处理后")
        self.image_display.mouse_pressed.connect(self.on_mouse_pressed)
        self.image_display.mouse_moved.connect(self.on_mouse_moved)
        self.image_display.mouse_released.connect(self.on_mouse_released)

        self.display_layout.addWidget(self.original_display)
        self.display_layout.addWidget(self.image_display)
        self.display_group.setLayout(self.display_layout)

        # 创建结果信息区域
        self.result_group = QGroupBox("结果信息")
        self.result_layout = QVBoxLayout()

        self.result_text = QLabel("结果将显示在这里")
        self.result_text.setWordWrap(True)

        self.result_layout.addWidget(self.result_text)
        self.result_group.setLayout(self.result_layout)

        # 创建右侧面板
        self.right_panel = QVBoxLayout()
        self.right_panel.addWidget(self.display_group)
        self.right_panel.addWidget(self.result_group)
        self.right_panel.setStretch(0, 3)
        self.right_panel.setStretch(1, 1)

        # 将左右面板添加到主布局
        self.main_layout.addLayout(self.left_panel, 1)
        self.main_layout.addLayout(self.right_panel, 3)

        # 连接滑块值变化信号
        self.brightness_slider.valueChanged.connect(
            lambda: self.brightness_value_label.setText(str(self.brightness_slider.value())))
        self.contrast_slider.valueChanged.connect(
            lambda: self.contrast_value_label.setText(str(self.contrast_slider.value())))
        self.saturation_slider.valueChanged.connect(
            lambda: self.saturation_value_label.setText(str(self.saturation_slider.value())))

        # 创建状态栏
        self.statusBar().showMessage("就绪")

    def load_image(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)"
        )
        if file_path:
            self.stop_video()
            self.is_cropping = False
            self.crop_btn.setText("开始裁剪")
            self.apply_crop_btn.setEnabled(False)
            self.cancel_crop_btn.setEnabled(False)

            processed, original = self.processor.load_image(file_path)
            if processed is not None:
                self.display_images(processed, original)
                self.statusBar().showMessage(f"已加载图像: {os.path.basename(file_path)}")
            else:
                QMessageBox.warning(self, "错误", "无法加载图像，请检查文件路径和格式")

    def load_video(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择视频", "", "视频文件 (*.mp4 *.avi *.mov)"
        )
        if file_path:
            self.stop_video()  # 停止可能存在的旧视频
            self.current_video_path = file_path
            self.statusBar().showMessage(f"已加载视频: {os.path.basename(file_path)}")

            # 初始化视频播放线程
            self.video_player = VideoPlayerThread(file_path)
            self.video_player.update_frame_signal.connect(self.display_video_frame)
            self.video_player.error_signal.connect(self.show_error_message)
            self.video_player.finished_signal.connect(self.on_video_finished)

            # 启用播放按钮
            self.play_pause_btn.setEnabled(True)
            self.stop_btn.setEnabled(True)

    def save_image(self):
        if self.processor.current_image is not None:
            file_path, _ = QFileDialog.getSaveFileName(
                self, "保存图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)"
            )
            if file_path:
                try:
                    cv2.imwrite(file_path, self.processor.current_image)
                    self.statusBar().showMessage(f"图像已保存至: {file_path}")
                except Exception as e:
                    QMessageBox.warning(self, "错误", f"保存图像失败: {str(e)}")
        else:
            QMessageBox.warning(self, "警告", "没有可保存的图像")

    def start_camera(self):
        self.is_cropping = False
        self.crop_btn.setText("开始裁剪")
        self.apply_crop_btn.setEnabled(False)
        self.cancel_crop_btn.setEnabled(False)
        self.compare_checkbox.setEnabled(False)  # 摄像头模式下禁用原图对比
        self.stop_video()

        self.video_thread = VideoThread(self.processor)
        self.video_thread.change_pixmap_signal.connect(self.display_video_frame)
        self.video_thread.error_signal.connect(self.show_error_message)
        self.video_thread.finished_signal.connect(self.on_camera_finished)

        self.video_thread.start()
        self.statusBar().showMessage("摄像头已打开")

    def stop_video(self):
        # 停止摄像头和视频播放
        if self.video_thread is not None:
            self.video_thread.stop()
            self.video_thread = None

        if self.video_player is not None:
            self.video_player.stop()
            self.video_player = None
            self.play_pause_btn.setText("播放")
            self.play_pause_btn.setEnabled(False)  # 禁用播放按钮，直到加载新视频
            self.stop_btn.setEnabled(False)
            self.current_video_path = ""

        self.compare_checkbox.setEnabled(True)  # 恢复原图对比功能
        self.image_display.clear()
        self.original_display.clear()
        self.statusBar().showMessage("视频已停止")

    def toggle_play_pause(self):
        if not self.current_video_path:
            QMessageBox.warning(self, "警告", "请先加载视频")
            return

        if self.video_player is None:
            # 初始化视频播放器
            self.video_player = VideoPlayerThread(self.current_video_path)
            self.video_player.update_frame_signal.connect(self.display_video_frame)
            self.video_player.error_signal.connect(self.show_error_message)
            self.video_player.finished_signal.connect(self.on_video_finished)

        if not self.video_player.isRunning():
            # 开始播放
            self.play_pause_btn.setText("暂停")
            self.video_player.start()
            self.statusBar().showMessage("视频播放中")
        else:
            # 暂停播放
            self.play_pause_btn.setText("播放")
            self.video_player.is_running = False  # 线程内循环会检测此标志
            self.statusBar().showMessage("视频已暂停")

    def display_images(self, processed, original):
        if processed is not None:
            # 显示处理后的图像
            try:
                rgb_processed = cv2.cvtColor(processed, cv2.COLOR_BGR2RGB)
                h, w, ch = rgb_processed.shape
                bytes_per_line = ch * w
                qimg_processed = QImage(rgb_processed.data, w, h, bytes_per_line, QImage.Format_RGB888)
                p_processed = qimg_processed.scaled(self.image_display.width(), self.image_display.height(),
                                                    Qt.KeepAspectRatio)
                self.image_display.setPixmap(QPixmap.fromImage(p_processed))

                # 显示原图（如果有且选项开启）
                if original is not None and self.show_original:
                    rgb_original = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)
                    qimg_original = QImage(rgb_original.data, rgb_original.shape[1], rgb_original.shape[0],
                                           rgb_original.shape[1] * 3, QImage.Format_RGB888)
                    p_original = qimg_original.scaled(self.original_display.width(), self.original_display.height(),
                                                      Qt.KeepAspectRatio)
                    self.original_display.setPixmap(QPixmap.fromImage(p_original))
                else:
                    self.original_display.clear()
                    self.original_display.setText("原图")
            except Exception as e:
                self.show_error_message(f"显示图像失败: {str(e)}")

    def display_video_frame(self, frame):
        if frame is not None:
            # 统一处理摄像头和视频的帧显示
            try:
                rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                h, w, ch = rgb_frame.shape
                bytes_per_line = ch * w
                qimg = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
                p = qimg.scaled(self.image_display.width(), self.image_display.height(), Qt.KeepAspectRatio)
                self.image_display.setPixmap(QPixmap.fromImage(p))
                self.original_display.clear()  # 视频模式下不显示原图
            except Exception as e:
                self.show_error_message(f"显示视频帧失败: {str(e)}")

    def rotate_image(self):
        angle = self.rotate_spinbox.value()
        processed, original = self.processor.rotate(angle)
        if processed is not None:
            self.display_images(processed, original)
            self.statusBar().showMessage(f"图像已旋转 {angle} 度")
        else:
            QMessageBox.warning(self, "警告", "请先加载图像")

    def resize_image(self):
        scale = self.resize_spinbox.value()
        processed, original = self.processor.resize(scale)
        if processed is not None:
            self.display_images(processed, original)
            self.statusBar().showMessage(f"图像已缩放至 {scale}%")
        else:
            QMessageBox.warning(self, "警告", "请先加载图像")

    def toggle_crop_mode(self):
        if self.processor.current_image is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        if self.video_thread is not None or self.video_player is not None:
            QMessageBox.warning(self, "警告", "请先停止视频")
            return

        self.is_cropping = not self.is_cropping

        if self.is_cropping:
            self.crop_btn.setText("取消裁剪模式")
            self.apply_crop_btn.setEnabled(False)
            self.cancel_crop_btn.setEnabled(True)
            self.statusBar().showMessage("裁剪模式已激活，请在图像上拖动选择区域")
        else:
            self.crop_btn.setText("开始裁剪")
            self.apply_crop_btn.setEnabled(False)
            self.cancel_crop_btn.setEnabled(False)
            processed, original = self.processor.cancel_crop()
            self.display_images(processed, original)
            self.statusBar().showMessage("裁剪模式已取消")

    def on_mouse_pressed(self, x, y):
        if self.is_cropping:
            # 转换为图像上的实际坐标
            pixmap = self.image_display.pixmap()
            if pixmap and not pixmap.isNull():
                img_width = pixmap.width()
                img_height = pixmap.height()
                display_width = self.image_display.width()
                display_height = self.image_display.height()

                # 计算图像在显示区域中的位置和缩放比例
                scale = min(display_width / img_width, display_height / img_height)
                img_display_width = img_width * scale
                img_display_height = img_height * scale
                x_offset = (display_width - img_display_width) // 2
                y_offset = (display_height - img_display_height) // 2

                # 检查是否点击在图像区域内
                if x >= x_offset and x < x_offset + img_display_width and y >= y_offset and y < y_offset + img_display_height:
                    # 计算在原始图像上的坐标
                    img_x = int((x - x_offset) / scale)
                    img_y = int((y - y_offset) / scale)

                    # 开始裁剪
                    self.processor.start_crop(img_x, img_y)

    def on_mouse_moved(self, x, y):
        if self.is_cropping and self.processor.crop_start is not None:
            # 转换为图像上的实际坐标
            pixmap = self.image_display.pixmap()
            if pixmap and not pixmap.isNull():
                img_width = pixmap.width()
                img_height = pixmap.height()
                display_width = self.image_display.width()
                display_height = self.image_display.height()

                scale = min(display_width / img_width, display_height / img_height)
                img_display_width = img_width * scale
                img_display_height = img_height * scale
                x_offset = (display_width - img_display_width) // 2
                y_offset = (display_height - img_display_height) // 2

                if x >= x_offset and x < x_offset + img_display_width and y >= y_offset and y < y_offset + img_display_height:
                    img_x = int((x - x_offset) / scale)
                    img_y = int((y - y_offset) / scale)

                    # 更新裁剪区域
                    processed, original = self.processor.update_crop(img_x, img_y)
                    if processed is not None:
                        self.display_images(processed, original)
                        self.apply_crop_btn.setEnabled(True)

    def on_mouse_released(self, x, y):
        # 鼠标释放时的处理，与移动时类似
        self.on_mouse_moved(x, y)

    def apply_crop(self):
        if self.is_cropping and self.processor.crop_start is not None and self.processor.crop_end is not None:
            processed, original = self.processor.apply_crop()
            if processed is not None:
                self.display_images(processed, original)
                self.is_cropping = False
                self.crop_btn.setText("开始裁剪")
                self.apply_crop_btn.setEnabled(False)
                self.cancel_crop_btn.setEnabled(False)
                self.statusBar().showMessage("裁剪已应用")

    def cancel_crop(self):
        if self.is_cropping:
            processed, original = self.processor.cancel_crop()
            self.display_images(processed, original)
            self.is_cropping = False
            self.crop_btn.setText("开始裁剪")
            self.apply_crop_btn.setEnabled(False)
            self.cancel_crop_btn.setEnabled(False)
            self.statusBar().showMessage("裁剪已取消")

    def adjust_color(self):
        brightness = self.brightness_slider.value()
        contrast = self.contrast_slider.value()
        saturation = self.saturation_slider.value()

        processed, original = self.processor.adjust_brightness_contrast(brightness, contrast, saturation)
        if processed is not None:
            self.display_images(processed, original)
            self.statusBar().showMessage(f"色彩调整已应用 (亮度: {brightness}, 对比度: {contrast}, 饱和度: {saturation})")
        else:
            QMessageBox.warning(self, "警告", "请先加载图像")

    def smooth_image(self):
        method_text = self.smooth_combo.currentText()
        kernel_size = self.smooth_spinbox.value()

        # 将方法文本映射到内部表示
        method_map = {
            "高斯模糊": "gaussian",
            "中值滤波": "median",
            "双边滤波": "bilateral"
        }
        method = method_map.get(method_text, "gaussian")

        processed, original = self.processor.smooth(method, kernel_size)
        if processed is not None:
            self.display_images(processed, original)
            self.statusBar().showMessage(f"{method_text} 已应用 (核大小: {kernel_size})")
        else:
            QMessageBox.warning(self, "警告", "请先加载图像")

    def detect_shapes(self):
        if self.processor.current_image is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        processed, original, shapes = self.processor.detect_shapes()
        if processed is not None:
            self.display_images(processed, original)
            result_text = f"检测到 {len(shapes)} 个形状: {', '.join(shapes)}"
            self.result_text.setText(result_text)
            self.statusBar().showMessage("形状识别完成")

    def detect_faces(self):
        if self.processor.current_image is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        processed, original, face_count = self.processor.detect_faces()
        if processed is not None:
            self.display_images(processed, original)
            result_text = f"检测到 {face_count} 个人脸"
            self.result_text.setText(result_text)
            self.statusBar().showMessage("人脸检测完成")

    def stitch_images(self):
        file_paths, _ = QFileDialog.getOpenFileNames(
            self, "选择要拼接的图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)"
        )
        if len(file_paths) < 2:
            QMessageBox.warning(self, "警告", "请选择至少两张图像进行拼接")
            return

        images = []
        for path in file_paths:
            img = cv2.imread(path)
            if img is not None:
                images.append(img)

        if not images:
            QMessageBox.warning(self, "警告", "无法加载任何图像")
            return

        result = self.processor.stitch_images(images)
        if result is not None:
            processed, _ = result  # 拼接后没有原始图像对比
            self.display_images(processed, None)
            self.statusBar().showMessage(f"已成功拼接 {len(images)} 张图像")
        else:
            QMessageBox.warning(self, "错误", "图像拼接失败")

    def apply_video_algorithm(self):
        if self.video_thread is None and self.video_player is None:
            QMessageBox.warning(self, "警告", "请先打开摄像头或加载视频")
            return

        algorithm = self.video_algorithm_combo.currentText()

        if self.video_thread is not None:
            # 摄像头模式
            self.stop_video()
            self.video_thread = VideoThread(self.processor, algorithm)
            self.video_thread.change_pixmap_signal.connect(self.display_video_frame)
            self.video_thread.error_signal.connect(self.show_error_message)
            self.video_thread.finished_signal.connect(self.on_camera_finished)
            self.video_thread.start()
            self.statusBar().showMessage(f"已应用 {algorithm} 算法到摄像头")
        elif self.video_player is not None:
            # 视频播放模式
            if algorithm == "None":
                self.video_player.set_processing_function(None)
            elif algorithm == "FaceDetection":
                self.video_player.set_processing_function(self.video_player.detect_faces_in_frame)
            elif algorithm == "ShapeDetection":
                self.video_player.set_processing_function(lambda frame: self.processor.detect_shapes()[0])

            # 重新启动视频播放器应用新算法
            was_playing = self.video_player.isRunning()
            if was_playing:
                self.video_player.stop()
                self.video_player.start()

            self.statusBar().showMessage(f"已应用 {algorithm} 算法到视频")

    def toggle_original_view(self, state):
        self.show_original = state == Qt.Checked
        if self.processor.current_image is not None:
            self.display_images(self.processor.current_image, self.processor.original_image)

    def show_error_message(self, message):
        """显示错误消息"""
        self.statusBar().showMessage(message)
        QMessageBox.warning(self, "错误", message)

    def on_video_finished(self):
        """视频播放结束处理"""
        self.statusBar().showMessage("视频播放完毕")
        self.play_pause_btn.setText("播放")
        self.play_pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)

    def on_camera_finished(self):
        """摄像头关闭处理"""
        self.statusBar().showMessage("摄像头已关闭")
        self.compare_checkbox.setEnabled(True)

    def closeEvent(self, event):
        """窗口关闭时释放资源"""
        self.stop_video()
        event.accept()


if __name__ == "__main__":
    # 确保中文显示正常
    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]

    app = QApplication(sys.argv)
    window = CVPlatform()
    window.show()
    sys.exit(app.exec_())